#include "calldll1.h"
#include "ui_calldll1.h"
#include <windows.h> // 为了使用Win32 API函数
#include <QMessageBox> // 为了使用MessageBox信息框
#include <QLibrary>
#include "Test1.h"

typedef void(*FUNC1)(); // 定义函数指针类型
typedef void(*FUNC2)(); // 定义函数指针类型
typedef int(*FUNC3)(TCHAR*,int); // 定义函数指针类型
extern int __declspec(dllimport)  gdllvar; // 在任何使用 DLL 的文件中，必须仍在数据的声明上使用 __declspec(dllimport)。

CallDLL1::CallDLL1(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::CallDLL1)
{
    ui->setupUi(this);
}

CallDLL1::~CallDLL1()
{
    delete ui;
}

/// Qt利用Win32 API调用DLL导出函数（_declspec(dllexport)导出）
void CallDLL1::on_pushButton_clicked()
{
    DWORD dw;
    HINSTANCE hDLL; // Handle to DLL
    FUNC1 lpfnDLLFunc1; // Function pointer
    QString str;

    /* 由于Qt项目默认在项目文控中采用Unicode编码，因此LoadLibrary()函数实际上被当作宽字符版本函数LoadLibraryW()，里面的参数字符串也必须是宽字符，需要加L（Win32编程中可以把普通字符串转为宽字符串的宏）。
     *   当然不用L也可以，但要把项目文控件置为窄字符集的项目文控，即去掉UNICODE控置，方法是在pro文控中添加DEFINES-= UNICODE。
     * 在64位的Windows系统中，一个64位的进程不能加载一个32位DLL。同理，一个32位的进程也不能加载一个64位的DLL。
     * 我们可以用DLL查看工具depends.exe来查看，这个是老牌的VC工具，虽然现在VC已经不自带这个工具了，但是依旧可以从官网上下载（http://www.dependencywalker.com/），
     *   而且新下载的版本可以查看64位的DLL，而原VC6版本自带的这个工具只能查看32位版本的DLL。
     * 从中可以看到Function下的导出函数名是“?f@@YAXXZ”。C++有重载功能，而且我们的Test项目是一个C++项目，所以导出的函数f()被C++编译器改名了，因为要支持重载。而我们现在在Qt中用了GetProcAddress()函数，
     *   它的第二个参数和Test.h的导出函数f()是一致的，所以就找不到了（因为Test.dll中并没有函数f()，只有?f@@YAXXZ）。那怎么才能让Test.dll导出函数f()呢？答案是用extern "C"。
     * __cplusplus是cpp中的自定义宏。定义了这个宏就表示这是一段cpp的代码，也就是说，上面代码的含义是：如果这是一段cpp的代码，那么加入extern "C"来处理{}中括起来的代码。
     * 在C++中，为了支持重载机制，在编译生成的汇编代码中，要对函数的名字进行一些处理，加入函数的返回类型等。而在C中，只是简单的函数名字而已，不会加入其他的信息，也就是说：C++和C对产生的函数名字的处理是不一样的。
     *   C++之父在控件C++之时，考虑到当时已经存在了大量的C代码，为了支持原来的C代码和已经写好的C库，需要在C++中尽可能支持C，extern "C"就是其中的一个策略。
     * 总结一下，在Qt程序中如果需要利用Win32 API函数（显示链接）调用DLL中的导出函数，就要注意DLL是否是64位的，另外在头文控中还要加extern "C"。
     * 如果不想在头文控中写extern "C"，那么其他的方法也是有的，就是让DLL开发者不用_declspec(dllexport)导出函数，而使用def来导出函数。
    */
    hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\x64\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    //hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    if(hDLL)
    {
        /* Win32 API函数GetProcAddress()不区分窄字符和宽字符版本，只有一个窄字符版本，因此第二个字符串前不需要加L。如果GetProcAddress()函数成功得到了导出函数的实际地址，就可以直接调用了。
         * 如果失败，我们将通过Win32 API函数GetLastError()来获得错误码，以便判断错误。
        */
        lpfnDLLFunc1 = (FUNC1)GetProcAddress(hDLL,"f"); // 得到导出函数的实际地址
        if(!lpfnDLLFunc1)
        {
            // 处理错误情况
            dw = ::GetLastError();
            FreeLibrary(hDLL);
            str.asprintf("GetProcAddress failed:%d",(int)dw);
            QMessageBox::information(this,"Error code",str);
        }
        else
        {
            lpfnDLLFunc1(); // 调用导出函数
            FreeLibrary(hDLL); // 释放句柄
        }
    }
    else
    {
        dw = ::GetLastError();
        str.asprintf("Load dll failed:%d",(int)dw);
        QMessageBox::information(this,"Error",str);
    }
}

/// Qt利用Win32 API调用DLL导出函数（def导出）
void CallDLL1::on_pushButton_2_clicked()
{
    DWORD dw;
    HINSTANCE hDLL; // Handle to DLL
    FUNC2 lpfnDLLFunc2; // Function pointer
    QString str;

    hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\x64\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    //hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    if(hDLL)
    {
        lpfnDLLFunc2 = (FUNC2)GetProcAddress(hDLL,"f2"); // 得到导出函数的实际地址
        if(!lpfnDLLFunc2)
        {
            // 处理错误情况
            dw = ::GetLastError();
            FreeLibrary(hDLL);
            str.asprintf("GetProcAddress failed:%d",(int)dw);
            QMessageBox::information(this,"Error code",str);
        }
        else
        {
            lpfnDLLFunc2(); // 调用导出函数
            FreeLibrary(hDLL); // 释放句柄
        }
    }
    else
    {
        dw = ::GetLastError();
        str.asprintf("Load dll failed:%d",(int)dw);
        QMessageBox::information(this,"Error",str);
    }
}

/// 使用QLibrary类调用DLL的导出函数
void CallDLL1::on_pushButton_3_clicked()
{
    /* 使用QLibrary类调用DLL导出函数对于调用DLL的方法，Qt（QLibrary）本身就有相应的类，用起来和Win32的步骤差不多。使用QLibrary类可以在程序运行时加载动态链接库。
     *   一个QLibrary类的实例作用于单个共享库上。QLibrary类提供了一种平台无关的方式访问库中的函数，既可以在构建QLibrary类的实例时将要加载的库文控传入，
     *   也可以在创建实例后调用setFileName()显式地控置要加载的库文控的文控名。当加载库文控时，QLibrary类会搜索所有平台特定库文控存放的位置，除非传入的文控名中具有绝对路径。
     * 所以，基于QLibrary对库文控的搜索机制，我们推荐在传入库文控时只传入该库文控的基本名，不写文控的前缀或后缀。这样一来，同一份代码可以运行于不同的操作系统中，并且该机制会保证进行最小次数的搜索。
     * QLibrary类中最重要的函数是load()，该函数动态地加载库文控，而函数isLoaded()可以用来检查库文控是否成功加载，resolve()函数则用来解析库中的符号地址，主要就是函数的地址。
     *   并且，resolve()函数在库文控未被加载时会隐式地尝试加载它。多个QLibrary类的实例可以访问同一个库文控。因为库文控一旦被加载，就会驻留在内存中，直到应用程序终止。
     *   当然，我们可以调用unload()函数来尝试卸载一个库文控，如果有其他QLibrary类的实例正在引用同一个库文控，unload()函数就会调用失败，并且该库文控会在所有实例都调用了unload()函数之后才被卸载。
     * QLibrary库的典型用法是去解析一个库中的导出符号，并调用该符号表示的C函数。这被称为“显式链接”。该过程和上面用Win32 API函数LoadLibrary()加载后再解析的过程一样。
     * 出于方便，该类还提供了一个静态的resolve()函数，我们可以使用该函数来解析并调用一个库中的函数，而不需要先加载该库.
     * 除了静态的resolve()函数，该类还提供了一个静态的isLibrary()函数，该函数可以根据特定平台来判断一个文控是否为可被加载的库.
    */
    int res;
    FUNC3 myfunc3;
    wchar_t chs[] = L"hello world";

    QLibrary lib("C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\x64\\Debug\\Win32DLLDemo.dll");
    if(lib.load())
    {
        myfunc3 = (FUNC3)lib.resolve("f1");
        if(!myfunc3)
        {
            QMessageBox::information(this,"Error","resolve failed");
        }
        else
        {
            res = myfunc3(chs,6);
        }
    }
    else
    {
        QMessageBox::information(this,"Error","Load dll failed");
    }
}

/// 直接法调用DLL中的导出变量
void CallDLL1::on_pushButton_4_clicked()
{
    /* 直接调用 DLL中的导出函数这一种方法比较简单，几乎不需要编码。首先把VC生成的导入库（.lib）文控放到Qt项目的项目目录下，把动态链接库（.dll）文控放到可执行程序的同一个目录下。
     *   然后在Qt项目的项目配置文控中指定导入库（.lib）文控的位置（既可以提供绝对路径，也可以提供相对路径），比如当前项目路径下有一个导入库文控，我们可以在Qt的项目配置文控（.pro）中添加如下相对路径：
     *   LIBS+=-L$$PWD/./ -lTest
     *   其中，选项-L用来指定导入库文控的路径；$$PWD表示当前路径，在Qt项目中，表示项目路径。值得注意的是，每次修改了Qt项目配置文控（.pro）中的内容后，都要“重新构建所有项目”，
     *   而后按Ctrl+R快捷键来运行项目，否则可能不会生效。最后在Qt项目中添加相应的头文控（和VC DLL项目的头文控内容相同，但不要直接复制过来，因为编码可能不同，建议新建，然后复制内容），
     *   这样就可以在需要的地方调用头文控中的导出变量了。
    */
    f();
}

/// 可视化法调用DLL中的导出函数
void CallDLL1::on_pushButton_5_clicked()
{
    /* 先将.lib和.dll分别放到项目根目录和项目可执行程序目录。
     * 在Qt Creator的项目视图中，右击树控件的根节点call，在弹出的快捷菜单中选择“添加库”选项，如图7-27所示。此时会出现“添加库”对话框，选择“外部库”，然后单击“下一步”按钮。
     *   而后会出现用于选择库路径的“添加库”对话框，在该对话框中取消对“平台”下的“Linux”和“Mac”选项的勾选，然后在“库文控”右边单击“浏览”按钮，选择本项目目录下的Test.lib，
     *   再取消对“为debug版本添加'd'作为后缀”选项的勾选，如图7-28所示。
    */
    f();
}

/// Qt利用Win32 API调用DLL导出变量
void CallDLL1::on_pushButton_6_clicked()
{
    DWORD dw;
    HINSTANCE hDLL; // Handle to DLL
    int *gdllvar;
    QString str;
    int n;

    hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\x64\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    //hDLL = ::LoadLibrary(L"C:\\MyStudy\\Qt\\QtGit\\qt-c4-bdemo-collection\\Win32DLLDemo\\Debug\\Win32DLLDemo.dll"); // 注意别少了L
    if(hDLL)
    {
        gdllvar = (int*)GetProcAddress(hDLL,"gdllvar"); // 得到导出变量的实际地址
        if(!gdllvar)
        {
            // 处理错误情况
            dw = ::GetLastError();
            FreeLibrary(hDLL);
            str = QString("GetProcAddress failed:%1").arg((int)dw);
            QMessageBox::information(this,"Error code",str);
        }
        else
        {
            n = *gdllvar;
            str = QString::number(n);
            QMessageBox::information(this,"result",str);
            FreeLibrary(hDLL); // 释放句柄
        }
    }
    else
    {
        dw = ::GetLastError();
        str = QString("Load dll failed:%1").arg((int)dw);
        QMessageBox::information(this,"Error",str);
    }
}

/// 使用QLibrary类调用DLL的导出变量
void CallDLL1::on_pushButton_7_clicked()
{
    int n,*pn;
    QString str;

    /* 使用相对路径构造QLibrary的方法？：
     *   1.将Win32DLLDemo.dll放到Qt的构建目录（可执行程序所在的目录）下，如：C:\MyStudy\Qt\QtGit\qt-c4-bdemo-collection\build-QtCallWin32DLLDemo-Desktop_Qt_5_14_2_MinGW_64_bit-Debug
    */
    QLibrary lib("Win32DLLDemo");
    if(lib.load())
    {
        pn = (int*)lib.resolve("gdllvar");
        if(!pn)
        {
            QMessageBox::information(this,"Error","resolve failed");
        }
        else
        {
            n = *pn;
            str = QString::number(n);
            QMessageBox::information(this,"result",str);
        }
    }
    else
    {
        QMessageBox::information(this,"Error","Load dll failed");
    }
}

/// 利用直接法使用DLL中的导出类?貌似不行吧？
void CallDLL1::on_pushButton_8_clicked()
{

}

extern "C"
{
    int add(int,int);
}
/// 利用可视化法调用Qt生成的DLL
void CallDLL1::on_pushButton_9_clicked()
{
//    QString str;
//    int sum = add(2,3);
//    str = QString::number(sum);
//    QMessageBox::information(this,"result",str);
}
